package ar.edu.caece.compiler.grammar.attributes.tree;

import java.util.List;

import ar.edu.caece.compiler.grammar.attributes.DeclaracionParametro;


public class NodeLlamadaFuncion extends NodeSentencia {
	private NodeParametrosFuncion nodeParametrosFuncion;
	private NodeFuncProc destino;
	private String nombreFuncion;
	private String labelFuncionLlamada;
	
	public NodeLlamadaFuncion() {

	}
	
	
	
	public NodeLlamadaFuncion(String id) {
		//this.addAttribute(Attribute.NOMBRE_FUNCION, new StringAttribute(id));
	}

	public void setNodeParametrosFuncion(NodeParametrosFuncion nodeParametrosFuncion) {
		this.nodeParametrosFuncion = nodeParametrosFuncion;
	}

	public NodeParametrosFuncion getNodeParametrosFuncion() {
		return nodeParametrosFuncion;
	}

	@Override
	public String toString() {
		return "NodeLlamadaFuncion [labelFuncion: " +labelFuncionLlamada + " nombreFuncion: "+nombreFuncion+"nodeParametrosFuncion="
				+ nodeParametrosFuncion + " entorno superior: " +entornoSuperior.getNombreProc() + " destino: " + destino.getNombreProc() + "]";
	}
	
	
	private String pushParametros() {
		//Tengo que ver si el par�metro es por valor o por referencia.
		//Si es por referencia, paso la posici�n de memoria.
		//Si es por valor, tengo que calcular y pasar la direcci�n de memoria donde qued� el c�lculo.
		
		//Buscar una forma com�n en donde tener los par�metros, ya que son son expresiones debo hacer el c�lculo
		//dejando la pila como estaba, y hacerlo antes del push.
		
		StringBuilder ret = new StringBuilder();
		
		//Obtengo los par�metros formales a partir de la declaraci�n.
		List<DeclaracionParametro> parametros = destino.getParametros();
		int i=0;
		if(parametros.size()>0) {
			//Moment�neamente restauro el bp para que los par�metros lo usan si lo necesitan
			//guardo el bp actual (del nuevo registro de activaci�n) en dx
			ret.append("mov dx, bp;restauro bp momentaneamente").append(System.getProperty("line.separator"))
			//Pusheo el bp nuevo para no perderlo, y ax con el bp viejo
			.append("push ax").append(System.getProperty("line.separator"))
			//Hago pop a bp para usarlo con los par�metros
			.append("pop bp").append(System.getProperty("line.separator"));
			for(DeclaracionParametro parametro : parametros) {
				//Si es por referencia, busco la posici�n de memoria.
				NodeExpr param = nodeParametrosFuncion.getParametros().get(i);
				if(!parametro.isPorValor()) {
					
					//Si es por referencia, tiene que ser una variable. Si no lo es, lola.
					NodeVariable variableParametro = (NodeVariable) param;
					
					//Obtengo el orden de la variable y la busco a partir del base pointer.
					//El base pointer lo tengo guardado en ax (ya cambi� el bp por sp)
					int orden = variableParametro.getOrdenVariable();
					
					ret.append("mov bx, ax;base pointer anterior").append(System.getProperty("line.separator"));
					ret.append("sub bx, 10;me coloco por encima de los temporales").append(System.getProperty("line.separator"));
					//Si es par�metro me corro al espacio de par�metros
					if(variableParametro.isParametro()) ret.append("sub bx, ").append(2*entornoSuperior.cantidadVariablesLocales()+8).append(System.getProperty("line.separator"));
					ret.append("sub bx, "+(1+orden)*2+";elijo la variable correspondiente").append(System.getProperty("line.separator"));
					ret.append("push bx;referencia");//paso la direcci�n de memoria
				} else {
					//Sino, es por valor. Me fijo si es un valor, y lo inserto. Sino, tengo que calcular e insertar.
					if(param.isValor()) {
						//push del valor sin nada m�s.
						ret.append("push "+((NodeValor)param).getValor().replace("n", ""));
					} else {
						//TODO Si es una operaci�n tengo que llamar a la funci�n correspondiente y hacer pop de la pila
						ret.append(param.generateCode());
						ret.append("push cx").append(System.getProperty("line.separator"));
						//El resultado deber�a quedar apilado por la llamda a la rutina.
					}
				}
				ret.append(System.getProperty("line.separator"));
				i++;
			}
			//Restauro el bp nuevo. La pila tiene los par�metros al final
			ret.append("push dx;push dx, registro de activaci�n nuevo guardado").append(System.getProperty("line.separator"));
			ret.append("pop bp;restauro bp de nuevo registro de activaci�n").append(System.getProperty("line.separator"));
		}
		return ret.toString();
	}

	private String armarRegistroActivacion() {
		
		StringBuilder registroActivacionBuilder = new StringBuilder();
		int variables = destino.cantidadVariablesLocales();
		
		return 	registroActivacionBuilder
				
				.append(";armo registro activacion" ).append(System.getProperty("line.separator"))	
				//Push ax, para no perderlo
				.append("push ax;Push ax, para no perderlo").append(System.getProperty("line.separator"))
				.append("mov ax, bp;Push bp a ax, para no perderlo").append(System.getProperty("line.separator"))
				//Push bp a ax, para no perderlo
				.append("mov bp, sp").append(System.getProperty("line.separator"))
				/*Piderle a la ra�z la funci�n destino a trav�s del label. 
				De ah� sacar la cantidad de variables que tiene.*/
				.append("sub sp, ").append(Integer.toString(10+variables*2)+";reservo temporales y variables locales"+System.getProperty("line.separator"))
				.append("pushf;push estado flags").append(System.getProperty("line.separator"))
				//push datalink: ser� bp anterior (en ax) si lo llama el padre.
				//TODO: ver qu� pasa si lo llama el hermano.
				.append("push ax;push data link").append(System.getProperty("line.separator"))
				//push control link. es el BP anterior, guardado en ax.
				.append("push ax;push control link y luego parametros").append(System.getProperty("line.separator"))

				//Push par�metros.
				.append(pushParametros())
				
				//Dejo un espacio para el valor de retorno. No deber�a si es procedimiento. Pero cualquier cosa hago siemrpe pop bx y listo
				.append("sub sp, 2;espacio etorno").append(System.getProperty("line.separator"))
				//Llamo al procedimiento
				.append("call ").append(labelFuncionLlamada).append(System.getProperty("line.separator"))
				//me quedo con el retorno en cx
				.append("pop cx;retorno en cx").append(System.getProperty("line.separator"))
				//Me deshago de los par�metros pasados
				.append("add sp, ").append(destino.getParametros().size()*2).append(System.getProperty("line.separator"))
				//me quedo con bp a partir del control link
				.append("pop bp;restauro bp").append(System.getProperty("line.separator"))
				//Me deshago del datalink
				.append("add sp,2;chau datalink").append(System.getProperty("line.separator"))
				//Me quedo con los flags
				.append("popf").append(System.getProperty("line.separator"))
				//Elimino temporales y variables
				.append("add sp, ").append(Integer.toString(10+variables*2)+System.getProperty("line.separator"))
				//Restauro bp a partir de ax.
				.append("mov bp, ax").append(System.getProperty("line.separator"))
				//restauro ax anterior
				.append("pop ax").append(System.getProperty("line.separator"))
				.toString();
	}
	
	@Override
	public String generateCode() {
		return armarRegistroActivacion();
	}

	public void setEntornoSuperior(NodeFuncProc entornoSuperior) {
		this.entornoSuperior = entornoSuperior;
	}
	


	public NodeFuncProc getEntornoSuperior() {
		return entornoSuperior;
	}

	public void setNombreFuncion(String nombreFuncion) {
		this.nombreFuncion = nombreFuncion;
	}

	public String getNombreFuncion() {
		return nombreFuncion;
	}

	public void setLabelFuncionLlamada(String labelFuncionLlamada) {
		this.labelFuncionLlamada = labelFuncionLlamada;
	}

	public String getLabelFuncionLlamada() {
		return labelFuncionLlamada;
	}
	
	@Override
	public void propagarEntornoSuperior(NodeFuncProc nodeFuncProc) {
		setEntornoSuperior(nodeFuncProc);
		nodeParametrosFuncion.propagarEntornoSuperior(nodeFuncProc);
	}
	
	@Override
	public void propagateRoot(NodeListaBloques raiz) {
		this.setRaiz(raiz);
		destino = raiz.getFuncProc(labelFuncionLlamada);
		if(destino==null) {
			//Algo sali� mal
			destino = new NodeFuncProc();
			destino.setNombreProc("ERROR");
		}
	}

	public void setDestino(NodeFuncProc destino) {
		this.destino = destino;
	}

	public NodeFuncProc getDestino() {
		return destino;
	}


}
